<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">



  
  
    
    
  <script src="/lib/pace/pace.min.js?v=1.0.2"></script>
  <link href="/lib/pace/pace-theme-minimal.min.css?v=1.0.2" rel="stylesheet">







<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="ios," />





  <link rel="alternate" href="/atom.xml" title="搁浅丶岁月的变迁" type="application/atom+xml" />






<meta name="description" content="本文转载自美图点评技术团队的：深入理解Objective-C：Category，略有修改。  前言无论一个类设计的多么完美，在未来的需求演进中，都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢？一般而言，继承和组合是不错的选择。但是在Objective-C 2.0中，又提供了category这个语言特性，可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码">
<meta name="keywords" content="ios">
<meta property="og:type" content="article">
<meta property="og:title" content="Objective-C：Category">
<meta property="og:url" content="http://yoursite.com/2016/12/21/2016-12-21-Objective-C-Category/index.html">
<meta property="og:site_name" content="搁浅丶岁月的变迁">
<meta property="og:description" content="本文转载自美图点评技术团队的：深入理解Objective-C：Category，略有修改。  前言无论一个类设计的多么完美，在未来的需求演进中，都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢？一般而言，继承和组合是不错的选择。但是在Objective-C 2.0中，又提供了category这个语言特性，可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="http://tech.meituan.com/img/diveintocategory/project.png">
<meta property="og:image" content="http://tech.meituan.com/img/diveintocategory/environment_vars.png">
<meta property="og:image" content="http://tech.meituan.com/img/diveintocategory/compile1.png">
<meta property="og:image" content="http://tech.meituan.com/img/diveintocategory/compile2.png">
<meta property="og:updated_time" content="2018-03-08T07:33:10.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Objective-C：Category">
<meta name="twitter:description" content="本文转载自美图点评技术团队的：深入理解Objective-C：Category，略有修改。  前言无论一个类设计的多么完美，在未来的需求演进中，都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢？一般而言，继承和组合是不错的选择。但是在Objective-C 2.0中，又提供了category这个语言特性，可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码">
<meta name="twitter:image" content="http://tech.meituan.com/img/diveintocategory/project.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://yoursite.com/2016/12/21/2016-12-21-Objective-C-Category/"/>





  <title>Objective-C：Category | 搁浅丶岁月的变迁</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>
    <a href="https://github.com/liaoren512"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_red_aa0000.png" alt="Fork me on GitHub"></a>
    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">搁浅丶岁月的变迁</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">没有虽败犹荣，只有成王败寇。</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://yoursite.com/2016/12/21/2016-12-21-Objective-C-Category/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="廖壬">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/ichgo.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="搁浅丶岁月的变迁">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">Objective-C：Category</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2016-12-21T00:00:00+08:00">
                2016-12-21
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          
            <span class="post-meta-divider">|</span>
            <span class="page-pv"><i class="fa fa-file-o"></i>
            <span class="busuanzi-value" id="busuanzi_value_page_pv" ></span>
            </span>
          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <blockquote>
<p>本文转载自美图点评技术团队的：<a href="http://tech.meituan.com/DiveIntoCategory.html" target="_blank" rel="noopener">深入理解Objective-C：Category</a>，略有修改。</p>
</blockquote>
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>无论一个类设计的多么完美，在未来的需求演进中，都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢？一般而言，继承和组合是不错的选择。但是在Objective-C 2.0中，又提供了category这个语言特性，可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码的各个角落，从Apple官方的framework到各个开源框架，从功能繁复的大型APP到简单的应用，catagory无处不在。本文对category做了比较全面的整理，希望对读者有所裨益。</p>
<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>本文系学习Objective-C的runtime源码时整理所成，主要剖析了category在runtime层的实现原理以及和category相关的方方面面，内容包括：</p>
<ul>
<li>初入宝地 category简介</li>
<li>连类比事 category和extension</li>
<li>挑灯细览 category真面目</li>
<li>追本溯源 category如何加载</li>
<li>旁枝末叶 category和+load方法</li>
<li>触类旁通 category和方法覆盖</li>
<li>更上一层 category和关联对象</li>
</ul>
<h2 id="初入宝地-Category简介"><a href="#初入宝地-Category简介" class="headerlink" title="初入宝地 Category简介"></a>初入宝地 Category简介</h2><p>Category是Objective-C 2.0之后添加的语言特性，Category的主要作用是为已经存在的类添加方法。除此之外，apple还推荐了Category的另外两个使用场景,详见<a href="https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html" target="_blank" rel="noopener">Apple Category文档</a>。</p>
<ul>
<li>可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处，<ul>
<li>可以减少单个文件的体积 b)可以把不同的功能组织到不同的category里 </li>
<li>可以由多个开发者共同完成一个类 </li>
<li>可以按需加载想要的 Category 等等。</li>
</ul>
</li>
<li>声明私有方法</li>
</ul>
<p>不过除了apple推荐的使用场景，广大开发者脑洞大开，还衍生出了category的其他几个使用场景：</p>
<ul>
<li>模拟多继承</li>
<li>把framework的私有方法公开</li>
</ul>
<p>Objective-C的这个语言特性对于纯动态语言来说可能不算什么，比如javascript，你可以随时为一个“类”或者对象添加任意方法和实例变量。但是对于不是那么“动态”的语言而言，这确实是一个了不起的特性。</p>
<h2 id="连类比事-Category和Extension"><a href="#连类比事-Category和Extension" class="headerlink" title="连类比事 Category和Extension"></a>连类比事 Category和Extension</h2><p>extension看起来很像一个匿名的category，但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议，它就是类的一部分，在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类，它伴随类的产生而产生，亦随之一起消亡。extension一般用来隐藏类的私有信息，你必须有一个类的源码才能为一个类添加extension，所以你无法为系统的类比如NSString添加extension。(详见<a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html" target="_blank" rel="noopener">Apple文档</a>)</p>
<h2 id="挑灯细览-category真面目"><a href="#挑灯细览-category真面目" class="headerlink" title="挑灯细览 category真面目"></a>挑灯细览 category真面目</h2><p>我们知道，所有的OC类和对象，在runtime层都是用struct表示的，category也不例外，在runtime层，category用结构体category_t（在objc-runtime-new.h中可以找到此定义），它包含了</p>
<ol>
<li>类的名字（name）</li>
</ol>
<ul>
<li>类（cls）</li>
<li>category中所有给类添加的实例方法的列表（instanceMethods）</li>
<li>category中所有添加的类方法的列表（classMethods）</li>
<li>category实现的所有协议的列表（protocols）</li>
<li>category中添加的所有属性（instanceProperties）</li>
</ul>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">category_t</span> &#123;</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *name;</span><br><span class="line">    <span class="keyword">classref_t</span> cls;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">method_list_t</span> *<span class="title">instanceMethods</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">method_list_t</span> *<span class="title">classMethods</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">protocol_list_t</span> *<span class="title">protocols</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">property_list_t</span> *<span class="title">instanceProperties</span>;</span></span><br><span class="line">&#125; <span class="keyword">category_t</span>;</span><br></pre></td></tr></table></figure>
<p>从category的定义也可以看出category的可为（可以添加实例方法，类方法，甚至可以实现协议，添加属性）和不可为（无法添加实例变量）。<br>ok，我们先去写一个category看一下category到底为何物：</p>
<p>MyClass.h：</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">MyClass</span> : <span class="title">NSObject</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)printName;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">MyClass</span>(<span class="title">MyAddition</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *name;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)printName;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>MyClass.m：</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">"MyClass.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MyClass</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)printName</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,<span class="string">@"MyClass"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MyClass</span>(<span class="title">MyAddition</span>)</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)printName</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,<span class="string">@"MyAddition"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>我们使用clang的命令去看看category到底会变成什么：</p>
<figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clang -<span class="built_in">rewrite</span>-objc MyClass.m</span><br></pre></td></tr></table></figure>
<p>好吧，我们得到了一个3M大小，10w多行的.cpp文件（这绝对是Apple值得吐槽的一点），我们忽略掉所有和我们无关的东西，在文件的最后，我们找到了如下代码片段：</p>
<figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">static struct <span class="comment">/*_method_list_t*/</span> &#123;</span><br><span class="line">unsigned int entsize;  <span class="comment">// sizeof(struct _objc_method)</span></span><br><span class="line">unsigned int method_count;</span><br><span class="line">struct <span class="variable">_objc_method</span> method_list[<span class="number">1</span>];</span><br><span class="line">&#125; <span class="variable">_OBJC_</span>$<span class="variable">_CATEGORY_INSTANCE_METHODS_MyClass_</span>$<span class="variable">_MyAddition</span> <span class="variable">__attribute__</span> ((used, section (<span class="string">"__DATA,__objc_const"</span>))) = &#123;</span><br><span class="line"><span class="built_in">sizeof</span>(<span class="variable">_objc_method</span>),</span><br><span class="line"><span class="number">1</span>,</span><br><span class="line">&#123;&#123;(struct objc_selector *)<span class="string">"printName"</span>, <span class="string">"v16@0:8"</span>, (void *)<span class="variable">_I_MyClass_MyAddition_printName</span>&#125;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">static struct <span class="comment">/*_prop_list_t*/</span> &#123;</span><br><span class="line">unsigned int entsize;  <span class="comment">// sizeof(struct _prop_t)</span></span><br><span class="line">unsigned int count_of_properties;</span><br><span class="line">struct <span class="variable">_prop_t</span> prop_list[<span class="number">1</span>];</span><br><span class="line">&#125; <span class="variable">_OBJC_</span>$<span class="variable">_PROP_LIST_MyClass_</span>$<span class="variable">_MyAddition</span> <span class="variable">__attribute__</span> ((used, section (<span class="string">"__DATA,__objc_const"</span>))) = &#123;</span><br><span class="line"><span class="built_in">sizeof</span>(<span class="variable">_prop_t</span>),</span><br><span class="line"><span class="number">1</span>,</span><br><span class="line">&#123;&#123;<span class="string">"name"</span>,<span class="string">"T@\"</span>NSString\<span class="string">",C,N"</span>&#125;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">extern <span class="string">"C"</span> <span class="variable">__declspec</span>(dllexport) struct <span class="variable">_class_t</span> OBJC_CLASS_$<span class="variable">_MyClass</span>;</span><br><span class="line"></span><br><span class="line">static struct <span class="variable">_category_t</span> <span class="variable">_OBJC_</span>$<span class="variable">_CATEGORY_MyClass_</span>$<span class="variable">_MyAddition</span> <span class="variable">__attribute__</span> ((used, section (<span class="string">"__DATA,__objc_const"</span>))) =</span><br><span class="line">&#123;</span><br><span class="line"><span class="string">"MyClass"</span>,</span><br><span class="line"><span class="number">0</span>, <span class="comment">// &amp;OBJC_CLASS_$_MyClass,</span></span><br><span class="line">(const struct <span class="variable">_method_list_t</span> *)&amp;<span class="variable">_OBJC_</span>$<span class="variable">_CATEGORY_INSTANCE_METHODS_MyClass_</span>$<span class="variable">_MyAddition</span>,</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line"><span class="number">0</span>,</span><br><span class="line">(const struct <span class="variable">_prop_list_t</span> *)&amp;<span class="variable">_OBJC_</span>$<span class="variable">_PROP_LIST_MyClass_</span>$<span class="variable">_MyAddition</span>,</span><br><span class="line">&#125;;</span><br><span class="line">static void OBJC_CATEGORY_SETUP_$<span class="variable">_MyClass_</span>$<span class="variable">_MyAddition</span>(void ) &#123;</span><br><span class="line"><span class="variable">_OBJC_</span>$<span class="variable">_CATEGORY_MyClass_</span>$<span class="variable">_MyAddition</span>.cls = &amp;OBJC_CLASS_$<span class="variable">_MyClass</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> section(<span class="meta-string">".objc_inithooks$B"</span>, long, read, write)</span></span><br><span class="line"><span class="variable">__declspec</span>(allocate(<span class="string">".objc_inithooks$B"</span>)) static void *OBJC_CATEGORY_SETUP[] = &#123;</span><br><span class="line">(void *)&amp;OBJC_CATEGORY_SETUP_$<span class="variable">_MyClass_</span>$<span class="variable">_MyAddition</span>,</span><br><span class="line">&#125;;</span><br><span class="line">static struct <span class="variable">_class_t</span> *L_OBJC_LABEL_CLASS_$ [<span class="number">1</span>] <span class="variable">__attribute__</span>((used, section (<span class="string">"__DATA, __objc_classlist,regular,no_dead_strip"</span>)))= &#123;</span><br><span class="line">&amp;OBJC_CLASS_$<span class="variable">_MyClass</span>,</span><br><span class="line">&#125;;</span><br><span class="line">static struct <span class="variable">_class_t</span> *<span class="variable">_OBJC_LABEL_NONLAZY_CLASS_</span>$[] = &#123;</span><br><span class="line">&amp;OBJC_CLASS_$<span class="variable">_MyClass</span>,</span><br><span class="line">&#125;;</span><br><span class="line">static struct <span class="variable">_category_t</span> *L_OBJC_LABEL_CATEGORY_$ [<span class="number">1</span>] <span class="variable">__attribute__</span>((used, section (<span class="string">"__DATA, __objc_catlist,regular,no_dead_strip"</span>)))= &#123;</span><br><span class="line">&amp;<span class="variable">_OBJC_</span>$<span class="variable">_CATEGORY_MyClass_</span>$<span class="variable">_MyAddition</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>我们可以看到，</p>
<ol>
<li>首先编译器生成了实例方法列表OBJC$_CATEGORY_INSTANCE_METHODSMyClass$_MyAddition和属性列表OBJC$_PROP_LISTMyClass$_MyAddition，两者的命名都遵循了公共前缀+类名+category名字的命名方式，而且实例方法列表里面填充的正是我们在MyAddition这个category里面写的方法printName，而属性列表里面填充的也正是我们在MyAddition里添加的name属性。还有一个需要注意到的事实就是category的名字用来给各种列表以及后面的category结构体本身命名，而且有static来修饰，所以在同一个编译单元里我们的category名不能重复，否则会出现编译错误。</li>
</ol>
<ul>
<li>其次，编译器生成了category本身OBJC$_CATEGORYMyClass$_MyAddition，并用前面生成的列表来初始化category本身。</li>
<li>最后，编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$（当然，如果有多个category，会生成对应长度的数组^_^），用于运行期category的加载。<br>到这里，编译器的工作就接近尾声了，对于category在运行期怎么加载，我们下节揭晓。</li>
</ul>
<h2 id="追本溯源-category如何加载"><a href="#追本溯源-category如何加载" class="headerlink" title="追本溯源 category如何加载"></a>追本溯源 category如何加载</h2><p>我们知道，Objective-C的运行是依赖OC的runtime的，而OC的runtime和其他系统库一样，是OS X和iOS通过dyld动态加载的。<br>想了解更多dyld地同学可以移步<a href="https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html" target="_blank" rel="noopener">这里</a>。</p>
<p>对于OC运行时，入口方法如下（在objc-os.mm文件中）：</p>
<figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">void</span> _objc_init(<span class="built_in">void</span>)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="built_in">bool</span> initialized = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">if</span> (initialized) <span class="keyword">return</span>;</span><br><span class="line">    initialized = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    // fixme defer initialization until an objc-<span class="keyword">using</span> image <span class="keyword">is</span> found?</span><br><span class="line">    environ_init();</span><br><span class="line">    tls_init();</span><br><span class="line">    lock_init();</span><br><span class="line">    exception_init();</span><br><span class="line"></span><br><span class="line">    // <span class="type">Register</span> <span class="keyword">for</span> unmap first, <span class="keyword">in</span> <span class="keyword">case</span> some +load unmaps something</span><br><span class="line">    _dyld_register_func_for_remove_image(&amp;unmap_image);</span><br><span class="line">    dyld_register_image_state_change_handler(dyld_image_state_bound,</span><br><span class="line">                                             <span class="number">1</span>/*batch*/, &amp;map_images);</span><br><span class="line">    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, <span class="number">0</span>/*<span class="keyword">not</span> batch*/, &amp;load_images);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>category被附加到类上面是在map_images的时候发生的，在new-ABI的标准下，_objc_init里面的调用的map_images最终会调用objc-runtime-new.mm里面的_read_images方法，而在_read_images方法的结尾，有以下的代码片段：</p>
<figure class="highlight xl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Discover categories. </span></span><br><span class="line">    <span class="keyword">for</span> (EACH_HEADER) &#123;</span><br><span class="line">        category_t **catlist =</span><br><span class="line">            _getObjc2CategoryList(hi, &amp;count);</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">            category_t *cat = catlist[i];</span><br><span class="line">            <span class="function"><span class="title">class_t</span> *cls = remapClass(cat-&gt;</span>cls);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!cls) &#123;</span><br><span class="line">                <span class="comment">// Category's target class is missing (probably weak-linked).</span></span><br><span class="line">                <span class="comment">// Disavow any knowledge of this category.</span></span><br><span class="line">                catlist[i] = NULL;</span><br><span class="line">                <span class="keyword">if</span> (PrintConnecting) &#123;</span><br><span class="line">                    _objc_inform(<span class="string">"CLASS: IGNORING category \?\?\?(%s) %p with "</span></span><br><span class="line">                                 <span class="string">"missing weak-linked target class"</span>,</span><br><span class="line">                                 <span class="function"><span class="title">cat</span>-&gt;</span><span class="keyword">name</span>, cat);</span><br><span class="line">                &#125;</span><br><span class="line">                continue;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Process this category. </span></span><br><span class="line">            <span class="comment">// First, register the category with its target class. </span></span><br><span class="line">            <span class="comment">// Then, rebuild the class's method lists (etc) if </span></span><br><span class="line">            <span class="comment">// the class is realized. </span></span><br><span class="line">            BOOL classExists = NO;</span><br><span class="line">            <span class="function"><span class="title">if</span> (cat-&gt;</span><span class="function"><span class="title">instanceMethods</span> ||  cat-&gt;</span>protocols </span><br><span class="line">                ||  <span class="function"><span class="title">cat</span>-&gt;</span>instanceProperties)</span><br><span class="line">            &#123;</span><br><span class="line">                addUnattachedCategoryForClass(cat, cls, hi);</span><br><span class="line">                <span class="keyword">if</span> (isRealized(cls)) &#123;</span><br><span class="line">                    remethodizeClass(cls);</span><br><span class="line">                    classExists = YES;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (PrintConnecting) &#123;</span><br><span class="line">                    _objc_inform(<span class="string">"CLASS: found category -%s(%s) %s"</span>,</span><br><span class="line">                                 <span class="function"><span class="title">getName</span>(cls), cat-&gt;</span><span class="keyword">name</span>,</span><br><span class="line">                                 classExists ? <span class="string">"on existing class"</span> : <span class="string">""</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="title">if</span> (cat-&gt;</span><span class="function"><span class="title">classMethods</span>  ||  cat-&gt;</span>protocols </span><br><span class="line">                <span class="comment">/* ||  cat-&gt;classProperties */</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="function"><span class="title">addUnattachedCategoryForClass</span>(cat, cls-&gt;</span>isa, hi);</span><br><span class="line">                <span class="function"><span class="title">if</span> (isRealized(cls-&gt;</span>isa)) &#123;</span><br><span class="line">                    <span class="function"><span class="title">remethodizeClass</span>(cls-&gt;</span>isa);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (PrintConnecting) &#123;</span><br><span class="line">                    _objc_inform(<span class="string">"CLASS: found category +%s(%s)"</span>,</span><br><span class="line">                                 <span class="function"><span class="title">getName</span>(cls), cat-&gt;</span><span class="keyword">name</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>首先，我们拿到的catlist就是上节中讲到的编译器为我们准备的category_t数组，关于是如何加载catlist本身的，我们暂且不表，这和category本身的关系也不大，有兴趣的同学可以去研究以下Apple的二进制格式和load机制。</p>
<p>略去PrintConnecting这个用于log的东西，这段代码很容易理解：</p>
<ol>
<li>把category的实例方法、协议以及属性添加到类上</li>
<li>把category的类方法和协议添加到类的metaclass上</li>
</ol>
<p>值得注意的是，在代码中有一小段注释 <code>/ || cat-&gt;classProperties /</code>，看来苹果有过给类添加属性的计划啊。<br>ok，我们接着往里看，category的各种列表是怎么最终添加到类上的，就拿实例方法列表来说吧：<br>在上述的代码片段里，addUnattachedCategoryForClass只是把类和category做一个关联映射，而remethodizeClass才是真正去处理添加事宜的功臣。</p>
<figure class="highlight xl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">static void remethodizeClass(class_t *cls)</span><br><span class="line">&#123;</span><br><span class="line">    category_list *cats;</span><br><span class="line">    BOOL isMeta;</span><br><span class="line"></span><br><span class="line">    rwlock_assert_writing(&amp;runtimeLock);</span><br><span class="line"></span><br><span class="line">    isMeta = isMetaClass(cls);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Re-methodizing: check for more categories</span></span><br><span class="line">    <span class="keyword">if</span> ((cats = unattachedCategoriesForClass(cls))) &#123;</span><br><span class="line">        chained_property_list *newproperties;</span><br><span class="line">        const protocol_list_t **newprotos;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (PrintConnecting) &#123;</span><br><span class="line">            _objc_inform(<span class="string">"CLASS: attaching categories to class '%s' %s"</span>,</span><br><span class="line">                         getName(cls), isMeta ? <span class="string">"(meta)"</span> : <span class="string">""</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Update methods, properties, protocols</span></span><br><span class="line"></span><br><span class="line">        BOOL vtableAffected = NO;</span><br><span class="line">        attachCategoryMethods(cls, cats, &amp;vtableAffected);</span><br><span class="line"></span><br><span class="line">        newproperties = buildPropertyList(NULL, cats, isMeta);</span><br><span class="line">        <span class="keyword">if</span> (newproperties) &#123;</span><br><span class="line">            <span class="function"><span class="title">newproperties</span>-&gt;</span><span class="function"><span class="title">next</span> = cls-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span>properties;</span><br><span class="line">            <span class="function"><span class="title">cls</span>-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span>properties = newproperties;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="title">newprotos</span> = buildProtocolList(cats, NULL, cls-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span>protocols);</span><br><span class="line">        <span class="function"><span class="title">if</span> (cls-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span><span class="function"><span class="title">protocols</span>  &amp;&amp;  cls-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span>protocols != newprotos) &#123;</span><br><span class="line">            _<span class="function"><span class="title">free_internal</span>(cls-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span>protocols);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function"><span class="title">cls</span>-&gt;</span><span class="function"><span class="title">data</span>()-&gt;</span>protocols = newprotos;</span><br><span class="line"></span><br><span class="line">        _free_internal(cats);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Update method caches and vtables</span></span><br><span class="line">        flushCaches(cls);</span><br><span class="line">        <span class="keyword">if</span> (vtableAffected) flushVtables(cls);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>而对于添加类的实例方法而言，又会去调用attachCategoryMethods这个方法，我们去看下attachCategoryMethods：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> </span><br><span class="line">attachCategoryMethods(<span class="keyword">class_t</span> *cls, category_list *cats,</span><br><span class="line">                      BOOL *inoutVtablesAffected)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!cats) <span class="keyword">return</span>;</span><br><span class="line">    <span class="keyword">if</span> (PrintReplacedMethods) printReplacements(cls, cats);</span><br><span class="line"></span><br><span class="line">    BOOL isMeta = isMetaClass(cls);</span><br><span class="line">    <span class="keyword">method_list_t</span> **mlists = (<span class="keyword">method_list_t</span> **)</span><br><span class="line">        _malloc_internal(cats-&gt;count * <span class="keyword">sizeof</span>(*mlists));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Count backwards through cats to get newest categories first</span></span><br><span class="line">    <span class="keyword">int</span> mcount = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">int</span> i = cats-&gt;count;</span><br><span class="line">    BOOL fromBundle = NO;</span><br><span class="line">    <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">        <span class="keyword">method_list_t</span> *mlist = cat_method_list(cats-&gt;<span class="built_in">list</span>[i].cat, isMeta);</span><br><span class="line">        <span class="keyword">if</span> (mlist) &#123;</span><br><span class="line">            mlists[mcount++] = mlist;</span><br><span class="line">            fromBundle |= cats-&gt;<span class="built_in">list</span>[i].fromBundle;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);</span><br><span class="line"></span><br><span class="line">    _free_internal(mlists);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>attachCategoryMethods做的工作相对比较简单，它只是把所有category的实例方法列表拼成了一个大的实例方法列表，然后转交给了attachMethodLists方法（我发誓，这是本节我们看的最后一段代码了^_^），这个方法有点长，我们只看一小段：</p>
<figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">for (uint32_t m = 0;</span><br><span class="line">             (scanForCustomRR || scanForCustomAWZ)  &amp;&amp;  m &lt; mlist-&gt;count;</span><br><span class="line">             m++)</span><br><span class="line">        &#123;</span><br><span class="line">            SEL sel = method_list_nth(mlist, m)-&gt;name;</span><br><span class="line">           <span class="built_in"> if </span>(scanForCustomRR  &amp;&amp;  isRRSelector(sel)) &#123;</span><br><span class="line">                cls-&gt;setHasCustomRR();</span><br><span class="line">                scanForCustomRR = false;</span><br><span class="line">            &#125; else<span class="built_in"> if </span>(scanForCustomAWZ  &amp;&amp;  isAWZSelector(sel)) &#123;</span><br><span class="line">                cls-&gt;setHasCustomAWZ();</span><br><span class="line">                scanForCustomAWZ = false;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        // Fill method list array</span><br><span class="line">        new<span class="class">Lists[newCount++] = mlist;</span></span><br><span class="line"><span class="keyword">    .</span></span><br><span class="line"><span class="keyword">    .</span></span><br><span class="line"><span class="keyword">    .</span></span><br><span class="line"></span><br><span class="line">    // Copy old methods to the method list array</span><br><span class="line">    for (i = 0; i &lt; oldCount; i++) &#123;</span><br><span class="line">        new<span class="class">Lists[newCount++] = oldLists[i];</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>需要注意的有两点：</p>
<ol>
<li>category的方法没有“完全替换掉”原来类已经有的方法，也就是说如果category和原来类都有methodA，那么category附加完成之后，类的方法列表里会有两个methodA</li>
<li>category的方法被放到了新方法列表的前面，而原来类的方法被放到了新方法列表的后面，这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法，这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的，它只要一找到对应名字的方法，就会罢休^_^，殊不知后面可能还有一样名字的方法。</li>
</ol>
<h2 id="旁枝末叶-category-和-load-方法"><a href="#旁枝末叶-category-和-load-方法" class="headerlink" title="旁枝末叶 category 和 +load 方法"></a>旁枝末叶 category 和 +load 方法</h2><p>我们知道，在类和category中都可以有+load方法，那么有两个问题：</p>
<ol>
<li>在类的+load方法调用的时候，我们可以调用category中声明的方法么？</li>
<li>这么些个+load方法，调用顺序是咋样的呢？</li>
</ol>
<p>鉴于上述几节我们看的代码太多了，对于这两个问题我们先来看一点直观的：</p>
<p><img src="http://tech.meituan.com/img/diveintocategory/project.png" alt=""></p>
<p>我们的代码里有MyClass和MyClass的两个category （Category1和Category2），MyClass和两个category都添加了+load方法，并且Category1和Category2都写了MyClass的printName方法。<br>在Xcode中点击Edit Scheme，添加如下两个环境变量（可以在执行load方法以及加载category的时候打印log信息，更多的环境变量选项可参见objc-private.h）:<br><img src="http://tech.meituan.com/img/diveintocategory/environment_vars.png" alt=""></p>
<p>运行项目，我们会看到控制台打印很多东西出来，我们只找到我们想要的信息，顺序如下：</p>
<figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">REPLACED</span>: -[<span class="symbol">MyClass</span> printName] by category <span class="symbol">Category1</span></span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">REPLACED</span>: -[<span class="symbol">MyClass</span> printName] by category <span class="symbol">Category2</span></span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: class <span class="string">'MyClass'</span> scheduled for +load</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: category <span class="string">'MyClass(Category1)'</span> scheduled for +load</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: category <span class="string">'MyClass(Category2)'</span> scheduled for +load</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: +[<span class="symbol">MyClass</span> load]</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: +[<span class="symbol">MyClass</span>(<span class="symbol">Category1</span>) load]</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: +[<span class="symbol">MyClass</span>(<span class="symbol">Category2</span>) load]</span><br></pre></td></tr></table></figure>
<p>所以，对于上面两个问题，答案是很明显的：</p>
<ol>
<li>可以调用，因为附加category到类的工作会先于+load方法的执行</li>
</ol>
<ul>
<li>+load的执行顺序是先类，后category，而category的+load执行顺序是根据编译顺序决定的。<br>目前的编译顺序是这样的：</li>
</ul>
<p><img src="http://tech.meituan.com/img/diveintocategory/compile1.png" alt=""></p>
<p>我们调整一个Category1和Category2的编译顺序，run。ok，我们可以看到控制台的输出顺序变了：</p>
<p><img src="http://tech.meituan.com/img/diveintocategory/compile2.png" alt=""></p>
<figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">REPLACED</span>: -[<span class="symbol">MyClass</span> printName] by category <span class="symbol">Category2</span></span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">REPLACED</span>: -[<span class="symbol">MyClass</span> printName] by category <span class="symbol">Category1</span></span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: class <span class="string">'MyClass'</span> scheduled for +load</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: category <span class="string">'MyClass(Category2)'</span> scheduled for +load</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: category <span class="string">'MyClass(Category1)'</span> scheduled for +load</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: +[<span class="symbol">MyClass</span> load]</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: +[<span class="symbol">MyClass</span>(<span class="symbol">Category2</span>) load]</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">objc[<span class="number">1187</span>]: <span class="symbol">LOAD</span>: +[<span class="symbol">MyClass</span>(<span class="symbol">Category1</span>) load]</span><br></pre></td></tr></table></figure>
<p>虽然对于+load的执行顺序是这样，但是对于“覆盖”掉的方法，则会先找到最后一个编译的category里的对应方法。</p>
<p>这一节我们只是用很直观的方式得到了问题的答案，有兴趣的同学可以继续去研究一下OC的运行时代码。</p>
<h2 id="触类旁通-category-和方法覆盖"><a href="#触类旁通-category-和方法覆盖" class="headerlink" title="触类旁通 category 和方法覆盖"></a>触类旁通 category 和方法覆盖</h2><p>鉴于上面几节我们已经把原理都讲了，这一节只有一个问题:</p>
<p>怎么调用到原来类中被category覆盖掉的方法？</p>
<p>对于这个问题，我们已经知道category其实并不是完全替换掉原来类的同名方法，只是category在方法列表的前面而已，所以我们只要顺着方法列表找到最后一个对应名字的方法，就可以调用原来类的方法：</p>
<figure class="highlight monkey"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">Class</span> <span class="title">currentClass</span> = [<span class="title">MyClass</span> <span class="title">class</span>];</span></span><br><span class="line">MyClass *my = [[MyClass alloc] init];</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (currentClass) &#123;</span><br><span class="line">    unsigned int methodCount;</span><br><span class="line">    <span class="function"><span class="keyword">Method</span> *<span class="title">methodList</span> =</span> class_copyMethodList(currentClass, &amp;methodCount);</span><br><span class="line">    IMP lastImp = <span class="literal">NULL</span>;</span><br><span class="line">    SEL lastSel = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">for</span> (NSInteger i = <span class="number">0</span>; i &lt; methodCount; i++) &#123;</span><br><span class="line">        <span class="function"><span class="keyword">Method</span> <span class="title">method</span> =</span> methodList[i];</span><br><span class="line">        NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(<span class="function"><span class="keyword">method</span>)) </span></span><br><span class="line">                                        encoding:NSUTF8StringEncoding];</span><br><span class="line">        <span class="keyword">if</span> ([@<span class="string">"printName"</span> isEqualToString:methodName]) &#123;</span><br><span class="line">            lastImp = method_getImplementation(<span class="function"><span class="keyword">method</span>);</span></span><br><span class="line">            lastSel = method_getName(<span class="function"><span class="keyword">method</span>);</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    typedef void (*fn)(id,SEL);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (lastImp != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        fn f = (fn)lastImp;</span><br><span class="line">        f(my,lastSel);</span><br><span class="line">    &#125;</span><br><span class="line">    free(methodList);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="更上一层-category-和关联对象"><a href="#更上一层-category-和关联对象" class="headerlink" title="更上一层 category 和关联对象"></a>更上一层 category 和关联对象</h2><p>如上所见，我们知道在category里面是无法为category添加实例变量的。但是我们很多时候需要在category中添加和对象关联的值，这个时候可以求助关联对象来实现。</p>
<p>MyClass+Category1.h:</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">"MyClass.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">MyClass</span> (<span class="title">Category1</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span>(<span class="keyword">nonatomic</span>,<span class="keyword">copy</span>) <span class="built_in">NSString</span> *name;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>MyClass+Category1.m:</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">"MyClass+Category1.h"</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&lt;objc/runtime.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MyClass</span> (<span class="title">Category1</span>)</span></span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)load</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@"%@"</span>,<span class="string">@"load in Category1"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)setName:(<span class="built_in">NSString</span> *)name</span><br><span class="line">&#123;</span><br><span class="line">    objc_setAssociatedObject(<span class="keyword">self</span>,</span><br><span class="line">                             <span class="string">"name"</span>,</span><br><span class="line">                             name,</span><br><span class="line">                             OBJC_ASSOCIATION_COPY);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="built_in">NSString</span>*)name</span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">NSString</span> *nameObject = objc_getAssociatedObject(<span class="keyword">self</span>, <span class="string">"name"</span>);</span><br><span class="line">    <span class="keyword">return</span> nameObject;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>但是关联对象又是存在什么地方呢？ 如何存储？ 对象销毁时候如何处理关联对象呢？<br>我们去翻一下runtime的源码，在objc-references.mm文件中有个方法_object_set_associative_reference：</p>
<figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="number">_</span><span class="keyword">object</span><span class="number">_</span>set<span class="number">_</span>associative<span class="number">_</span>reference(id <span class="keyword">object</span>, <span class="keyword">void</span> *key, id <span class="keyword">value</span>, uintptr<span class="number">_</span>t policy) &#123;</span><br><span class="line">    <span class="comment">// retain the new value (if any) outside the lock.</span></span><br><span class="line">    ObjcAssociation old<span class="number">_</span>association(<span class="number">0</span>, nil);</span><br><span class="line">    id <span class="keyword">new</span><span class="number">_</span><span class="keyword">value</span> = <span class="keyword">value</span> ? acquireValue(<span class="keyword">value</span>, policy) : nil;</span><br><span class="line">    &#123;</span><br><span class="line">        AssociationsManager manager;</span><br><span class="line">        AssociationsHashMap &amp;associations(manager.associations());</span><br><span class="line">        disguised<span class="number">_p</span>tr<span class="number">_</span>t disguised<span class="number">_</span><span class="keyword">object</span> = DISGUISE(<span class="keyword">object</span>);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">new</span><span class="number">_</span><span class="keyword">value</span>) &#123;</span><br><span class="line">            <span class="comment">// break any existing association.</span></span><br><span class="line">            AssociationsHashMap::iterator i = associations.find(disguised<span class="number">_</span><span class="keyword">object</span>);</span><br><span class="line">            <span class="keyword">if</span> (i != associations.end()) &#123;</span><br><span class="line">                <span class="comment">// secondary table exists</span></span><br><span class="line">                ObjectAssociationMap *refs = i-&gt;second;</span><br><span class="line">                ObjectAssociationMap::iterator j = refs-&gt;find(key);</span><br><span class="line">                <span class="keyword">if</span> (j != refs-&gt;end()) &#123;</span><br><span class="line">                    old<span class="number">_</span>association = j-&gt;second;</span><br><span class="line">                    j-&gt;second = ObjcAssociation(policy, <span class="keyword">new</span><span class="number">_</span><span class="keyword">value</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    (*refs)[key] = ObjcAssociation(policy, <span class="keyword">new</span><span class="number">_</span><span class="keyword">value</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// create the new association (first time).</span></span><br><span class="line">                ObjectAssociationMap *refs = <span class="keyword">new</span> ObjectAssociationMap;</span><br><span class="line">                associations[disguised<span class="number">_</span><span class="keyword">object</span>] = refs;</span><br><span class="line">                (*refs)[key] = ObjcAssociation(policy, <span class="keyword">new</span><span class="number">_</span><span class="keyword">value</span>);</span><br><span class="line">                <span class="number">_</span><span class="keyword">class</span><span class="number">_</span>setInstancesHaveAssociatedObjects(<span class="number">_</span><span class="keyword">object</span><span class="number">_</span>getClass(<span class="keyword">object</span>));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// setting the association to nil breaks the association.</span></span><br><span class="line">            AssociationsHashMap::iterator i = associations.find(disguised<span class="number">_</span><span class="keyword">object</span>);</span><br><span class="line">            <span class="keyword">if</span> (i !=  associations.end()) &#123;</span><br><span class="line">                ObjectAssociationMap *refs = i-&gt;second;</span><br><span class="line">                ObjectAssociationMap::iterator j = refs-&gt;find(key);</span><br><span class="line">                <span class="keyword">if</span> (j != refs-&gt;end()) &#123;</span><br><span class="line">                    old<span class="number">_</span>association = j-&gt;second;</span><br><span class="line">                    refs-&gt;erase(j);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// release the old value (outside of the lock).</span></span><br><span class="line">    <span class="keyword">if</span> (old<span class="number">_</span>association.hasValue()) ReleaseValue()(old<span class="number">_</span>association);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们可以看到所有的关联对象都由AssociationsManager管理，而AssociationsManager定义如下：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AssociationsManager</span> &#123;</span></span><br><span class="line">    <span class="keyword">static</span> OSSpinLock _lock;</span><br><span class="line">    <span class="keyword">static</span> AssociationsHashMap *_map;               <span class="comment">// associative references:  object pointer -&gt; PtrPtrHashMap.</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    AssociationsManager()   &#123; OSSpinLockLock(&amp;_lock); &#125;</span><br><span class="line">    ~AssociationsManager()  &#123; OSSpinLockUnlock(&amp;_lock); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">AssociationsHashMap &amp;<span class="title">associations</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (_map == <span class="literal">NULL</span>)</span><br><span class="line">            _map = <span class="keyword">new</span> AssociationsHashMap();</span><br><span class="line">        <span class="keyword">return</span> *_map;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址（任意两个不同对象的指针地址一定是不同的），而这个map的value又是另外一个AssociationsHashMap，里面保存了关联对象的kv对。<br>而在对象的销毁逻辑里面，见objc-runtime-new.mm:</p>
<figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> *objc<span class="number">_</span>destructInstance(id obj) </span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (obj) &#123;</span><br><span class="line">        Class isa<span class="number">_</span>gen = <span class="number">_</span><span class="keyword">object</span><span class="number">_</span>getClass(obj);</span><br><span class="line">        <span class="keyword">class</span><span class="number">_</span>t *isa = newcls(isa<span class="number">_</span>gen);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Read all of the flags at once for performance.</span></span><br><span class="line">        bool cxx = hasCxxStructors(isa);</span><br><span class="line">        bool assoc = !UseGC &amp;&amp; <span class="number">_</span><span class="keyword">class</span><span class="number">_</span>instancesHaveAssociatedObjects(isa<span class="number">_</span>gen);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// This order is important.</span></span><br><span class="line">        <span class="keyword">if</span> (cxx) <span class="keyword">object</span><span class="number">_</span>cxxDestruct(obj);</span><br><span class="line">        <span class="keyword">if</span> (assoc) <span class="number">_</span><span class="keyword">object</span><span class="number">_</span>remove<span class="number">_</span>assocations(obj);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!UseGC) objc<span class="number">_</span>clear<span class="number">_</span>deallocating(obj);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> obj;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>嗯，runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象，如果有，会调用_object_remove_assocations做关联对象的清理工作。</p>
<h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>正如侯捷先生所讲-“源码面前，了无秘密”，Apple的Cocoa Touch框架虽然并不开源，但是Objective-C的runtime和Core Foundation却是完全开放源码的(在<a href="http://www.opensource.apple.com/tarballs/" target="_blank" rel="noopener">http://www.opensource.apple.com/tarballs/</a>可以下载到全部的开源代码)。<br>本系列runtime源码学习将会持续更新，意犹未尽的同学可以自行到上述网站下载源码学习。行笔简陋，如有错误，望指正。</p>

      
    </div>
    
    
    

    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <!--<a href="/tags/ios/" rel="tag"># ios</a>-->
            <a href="/tags/ios/" rel="tag"><i class="fa fa-tag"></i> ios</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2016/12/13/2016-12-13-定时器你真的会使用吗？/" rel="next" title="定时器 你真的会使用吗？">
                <i class="fa fa-chevron-left"></i> 定时器 你真的会使用吗？
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2016/12/26/2016-12-26-ReactiveCocoa-基础/" rel="prev" title="ReactiveCocoa 基础">
                ReactiveCocoa 基础 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
        <!-- JiaThis Button BEGIN -->
<div class="jiathis_style">
<span class="jiathis_txt">分享到：</span>
<a class="jiathis_button_fav">收藏夹</a>
<a class="jiathis_button_copy">复制网址</a>
<a class="jiathis_button_email">邮件</a>
<a class="jiathis_button_weixin">微信</a>
<a class="jiathis_button_qzone">QQ空间</a>
<a class="jiathis_button_tqq">腾讯微博</a>
<a class="jiathis_button_douban">豆瓣</a>
<a class="jiathis_button_share">一键分享</a>

<a href="http://www.jiathis.com/share?uid=2140465" class="jiathis jiathis_txt jiathis_separator jtico jtico_jiathis" target="_blank">更多</a>
<a class="jiathis_counter_style"></a>
</div>
<script type="text/javascript" >
var jiathis_config={
  data_track_clickback:true,
  summary:"",
  shortUrl:false,
  hideMore:false
}
</script>
<script type="text/javascript" src="http://v3.jiathis.com/code/jia.js?uid=" charset="utf-8"></script>
<!-- JiaThis Button END -->
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/ichgo.jpg"
                alt="廖壬" />
            
              <p class="site-author-name" itemprop="name">廖壬</p>
              <p class="site-description motion-element" itemprop="description">Learn to choose, to forsake, to endure loneliness, to resist temptation.</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">391</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">11</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">56</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://github.com/liaoren512" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="mailto:liaoren512@gmail.com" target="_blank" title="E-Mail">
                      
                        <i class="fa fa-fw fa-envelope"></i>E-Mail</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://weibo.com/5281757990/profile?rightmod=1&wvr=6&mod=personinfo" target="_blank" title="Weibo">
                      
                        <i class="fa fa-fw fa-globe"></i>Weibo</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://www.zhihu.com" target="_blank" title="Zhihu">
                      
                        <i class="fa fa-fw fa-globe"></i>Zhihu</a>
                  </span>
                
            </div>
          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#简介"><span class="nav-number">2.</span> <span class="nav-text">简介</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#初入宝地-Category简介"><span class="nav-number">2.1.</span> <span class="nav-text">初入宝地 Category简介</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#连类比事-Category和Extension"><span class="nav-number">2.2.</span> <span class="nav-text">连类比事 Category和Extension</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#挑灯细览-category真面目"><span class="nav-number">2.3.</span> <span class="nav-text">挑灯细览 category真面目</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#追本溯源-category如何加载"><span class="nav-number">2.4.</span> <span class="nav-text">追本溯源 category如何加载</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#旁枝末叶-category-和-load-方法"><span class="nav-number">2.5.</span> <span class="nav-text">旁枝末叶 category 和 +load 方法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#触类旁通-category-和方法覆盖"><span class="nav-number">2.6.</span> <span class="nav-text">触类旁通 category 和方法覆盖</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#更上一层-category-和关联对象"><span class="nav-number">2.7.</span> <span class="nav-text">更上一层 category 和关联对象</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#后记"><span class="nav-number">3.</span> <span class="nav-text">后记</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        
<!--<script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>-->

<div class="copyright">&copy; <span itemprop="copyrightYear">2018</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">廖壬</span>

  
</div>

<!-- 在网站底部加上访问量-->
<!--<div class="powered-by">
<i class="fa fa-user-md"></i><span id="busuanzi_container_site_uv">
  本站访客数:<span id="busuanzi_value_site_pv"></span>
</span>
</div>-->
<!--pv的方式，单个用户连续点击n篇文章，记录n次访问量-->
<!--

  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>



-->
<div class="theme-info">
  <div class="powered-by"></div>
  <span class="post-count">博客全站共字</span>
</div>
        
<div class="busuanzi-count">
  <script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  
    <span class="site-uv">
      <i class="fa fa-user"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
      
    </span>
  

  
    <span class="site-pv">
      <i class="fa fa-eye"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
      
    </span>
  
</div>








        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  


  



  
  









  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  

  
  
    <script type="text/javascript" src="/lib/canvas-nest/canvas-nest.min.js"></script>
  

  
  
    <script type="text/javascript" src="/lib/three/three.min.js"></script>
  

  
  
    <script type="text/javascript" src="/lib/three/three-waves.min.js"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  

</body>
</html>
<!-- 页面点击小红心 -->
<script type="text/javascript" src="/js/src/love.js"></script>